<?php
/**
 * Created by PhpStorm.
 * User: jaylen
 * Date: 2020-06-05
 * Time: 14:28
 */

namespace app\common\service;


use app\common\exception\WxPayException;
use app\common\service\traits\WxPayNotify;
use think\facade\Config;
use think\facade\Log;
use think\facade\Request;
use WeChat\Contracts\Tools;

class WxPay
{
    private $config = null; // 配置参数
    private $wxchat = null; // 实例化接口

    use WxPayNotify;

    public function __construct()
    {
        // 准备公众号配置参数
        $this->config = Config::get('wxpay');
        // 添加appid和appsecret
        $this->config['appid'] = get_wx_config('mp_app_id');
        $this->config['appsecret'] = get_wx_config('mp_app_secret');

        if (empty($this->config)) {
            throw new WxPayException([
                'msg' => '获取微信支付配置失败',
                'errorCode' => 50100,
            ]);
        }

        // 创建接口实例
        $this->wxchat = \WeChat\Pay::instance($this->config);
    }

    /**
     * 调用统一下单接口
     * @param $body 商品的描述
     * @param $total_fee 标记金额
     * @param $openid 用户的openid
     * @param string $notify_url 支付成功微信服务器通知地址
     * @param string $attach 附带通知地址的参数
     * @return array
     */
    public function createOrder($body,
                                $total_fee,
                                $openid,
                                $notify_url,
                                $attach='')
    {
        // 组装参数，可以参考官方商户文档
        $options = [
            'body'             => $body,                                                // 商品描述
            'out_trade_no'     => $this->makeOrderNo(),                                 // 商户订单号
            'total_fee'        => $total_fee*100,                                       // 标价金额
            'openid'           => $openid,                                              // 用户标识
            'trade_type'       => 'JSAPI',                                              // 交易类型
            'notify_url'       => $notify_url,                                          // 通知地址
            'spbill_create_ip' => Request::ip(),                                        // 终端IP
            'attach'           => json_encode($attach, JSON_UNESCAPED_UNICODE), // 附加数据,在查询API和支付通知中原样返回，可作为自定义参数使用
        ];

        try {

            // 生成预支付码
            $result = $this->wxchat->createOrder($options);

            // 创建JSAPI参数签名并返回
            $resultOptions = $this->wxchat->createParamsForJsApi($result['prepay_id']);
            $resultOptions['order_no'] = $options['out_trade_no']; // 添加订单号信息
            $resultOptions['prepay_id'] = $result['prepay_id'];

            return $resultOptions;

        } catch (\Exception $e) {

            // 记录错误到系统日志中
            Log::record('拉起位置支付失败，错误信息为：' . $e->getMessage(),'error');

            throw new WxPayException();
        }
    }

    /**
     * 发起微信退款
     * @param $transaction_id 微信订单号
     * @param $total_fee 订单总价格
     * @param $refund_fee 订单退款金额
     * @param $notify_url 退款的通知地址
     * @param string $refund_desc 退款原因
     */
    public function refundOrder($transaction_id, $total_fee, $refund_fee, $notify_url, $refund_desc = '')
    {
        // 组装参数，可以参考官方商户文档
        $options = [
            'transaction_id' => $transaction_id,        // 微信订单号
            'out_refund_no'  => $this->makeOrderNo(),   // 商户退款单号
            'total_fee'      => $total_fee*100,             // 订单总金额
            'refund_fee'     => $refund_fee*100,            // 订单退款金额
            'refund_desc'    => $refund_desc,               // 订单退款原因
            'notify_url'     => $notify_url                 // 通知地址
        ];

        try {
            $result = $this->wxchat->createRefund($options);

            return $result;
        } catch (\Exception $e) {

            // 记录错误到系统日志中
            Log::record('发起微信退款失败，错误信息为：' . $e->getMessage(),'error');

            throw new WxPayException([
                'msg' => '发起微信退款失败',
                'errorCode' => 50102,
            ]);
        }
    }

    /**
     * 获取支付结果通知
     * @throws WxPayException
     */
    public function notifyOrder()
    {
        try {
            // 获取通知参数
            $data = $this->wxchat->getNotify();
            if ($data['return_code'] === 'SUCCESS' && $data['result_code'] === 'SUCCESS') {

                $this->handlePaySuccessNotify($data, true);

                // 返回接收成功的回复
                ob_clean();
                return $this->wxchat->getNotifySuccessReply();
            }
        } catch (\Exception $e) {
            Log::record('获取微信支付结果失败，失败信息为：' . $e->getMessage(),'error');
            throw new WxPayException([
                'msg' => '获取微信支付结果失败',
                'errorCode' => 50103
            ]);
        }
    }

    /**
     * 接收微信退款通知
     * @throws WxPayException
     */
    public function notifyRefundOrder()
    {
        try {
            // 获取通知参数
            $data = $this->getRefundNotify($this->config);

            if ($data['return_code'] === 'SUCCESS') {

                $this->handleRefundOrderNotify($data, true);

                // 返回接收成功的回复
                ob_clean();
                return $this->wxchat->getNotifySuccessReply();
            }
        } catch (\Exception $e) {
            $errorMsg = $e->getFile() . ':' . $e->getLine() . ' ' . $e->getMessage() . '(' . $e->getCode() . ')' . get_class($e);
            Log::record($errorMsg,'error');
            Log::record('获取微信退款结果失败，失败信息为：' . $e->getMessage(),'error');
            throw new WxPayException([
                'msg' => '获取微信退款结果失败',
                'errorCode' => 50104
            ]);
        }
    }

    /**
     * 生成内部订单编号
     * @return string
     */
    protected function makeOrderNo()
    {
        $yCode = array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J');
        $orderSn =
            $yCode[intval(date('Y')) - 2020] . strtoupper(dechex(date('m'))) . date(
                'd') . substr(time(), -5) . substr(microtime(), 2, 5) . sprintf(
                '%02d', rand(0, 99));
        return $orderSn;
    }

    /**
     * 生成内部退款订单编号
     * @return string
     */
    protected function makeRefundOrderNo()
    {
        $yCode = array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J');
        $orderSn =
            'F' . $yCode[intval(date('Y')) - 2020] . strtoupper(dechex(date('m'))) . date(
                'd') . substr(time(), -5) . substr(microtime(), 2, 5) . sprintf(
                '%02d', rand(0, 99));
        return $orderSn;
    }

    protected function getRefundNotify($config)
    {
        $data = Tools::xml2arr(file_get_contents("php://input"));
        if (!isset($data['return_code']) || $data['return_code'] !== 'SUCCESS') {
            throw new \Exception('获取退款通知XML失败！'. $data['return_msg']);
        }

        $key = md5($config['mch_key']);
        $req_info = base64_decode($data['req_info']);

        $xml = openssl_decrypt($req_info, 'AES-256-ECB', $key, OPENSSL_RAW_DATA, '');

        if (empty($xml)) {
            throw new \Exception('解析req_info内容失败');
        }

        $data['decode'] = $xml;

        return $data;

    }
}